﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Vowei.Core;
using Vowei.Core.Models;
using Vowei.Data;
using Vowei.Data.Models;
using System.Collections;
using Vowei.Data.Helpers;
using System.Text;

namespace Vowei.Data.Helpers
{
    public static class EditingHelpers
    {
        private static Guid CreatingObjectPrimaryKey = Guid.Empty;

        internal static object[] EmptyParams = new object[] { };

        public static U GenericEndEdit<U>(U obj, IVoweiContext context, IUser user, string[] ignoreProperties)
            where U : class, INamedTable, ISupportDefaultProperties, new()
        {
            if (obj == null)
                throw new ArgumentNullException("obj");

            var table = context.Resolve<U>();
            Comment history = null;
            ICommentable commentable = obj as ICommentable;

            if (!table.Query.Any(o => o.ID == obj.ID))
            {
                obj.Reporter = user.GetIdentifier();
                table.Add(obj);
                history = TrackAddObject(obj, user);
            }
            else
            {
                var objEditing = EntityHelpers.GenericGetObjectDetails<U>(obj.ID, context);
                history = objEditing.ApplyEditing(obj, user, ignoreProperties);
                commentable = objEditing as ICommentable;

                obj = objEditing;
            }

            obj.LastModified = DateTime.Now;
            obj.LastModifiedBy = user.GetIdentifier();

            if (commentable != null)
                commentable.Comments.Add(history);
            else
                context.Comments.Add(history);

            context.SaveChanges();

            return obj;
        }

        public static U GenericEndEdit<U>(U obj, IVoweiContext context, IUser user)
            where U : class, INamedTable, ISupportDefaultProperties, new()
        {
            return GenericEndEdit(obj, context, user, null);
        }

        public static Comment TrackingChange(
            this ICommentable item, string message, IUser user)
        {
            var comment = new Comment()
            {
                PostId = item.ID,
                PostType = EntityHelpers.GetEntityTypeName(item.GetType()),
                Text = message,
                User = user.GetIdentifier()
            };
            item.Comments.Add(comment);

            return comment;
        }

        public static string[] SplitUsers(string usersRaw)
        {
            if (string.IsNullOrEmpty(usersRaw))
                throw new ArgumentNullException("请输入要添加的用户名");

            usersRaw = usersRaw.Trim();
            if (string.IsNullOrEmpty(usersRaw))
                throw new ArgumentNullException("请输入要添加的用户名");

            var users = usersRaw.Split(';', ',');
            return users;
        }

        private static Comment ApplyEditing<T>(this T tobeEdit, T other, IUser user, string[] ignoreProperties)
            where T : INamedTable
        {
            if (object.Equals(other, default(T)))
                return null;

            if (tobeEdit == null)
                throw new ArgumentException("tobeEdit");
            if (other == null)
                throw new ArgumentException("other");

            var properties = GetRidOfDefaultProperties(tobeEdit, ignoreProperties);
            var history = CreateHistoryHelper(tobeEdit, user);
            var historyText = new StringBuilder();
            var keyProperties = new Dictionary<string, PropertyInfo>();  // 包含外键、主键等属性信息

            foreach (var property in properties)
            {
                var propEditing = property.GetValue(tobeEdit, EmptyParams);
                var propOther = property.GetValue(other, EmptyParams);

                if (!IsCollectionProperty(property.PropertyType))
                {
                    // 一般来说，Guid要么是主键，要么是外键，因此避免对其变更做出反应。
                    if (property.PropertyType == typeof(Guid) ||
                        property.PropertyType == typeof(Nullable<Guid>))
                    {
                        keyProperties.Add(property.Name, property);
                        continue;
                    }

                    if (AreEquals(propEditing, propOther))
                        continue;

                    if (property.PropertyType == typeof(DateTime) &&
                        AreDateTimeEquals((DateTime)propEditing, (DateTime)propOther))
                    {
                        continue;
                    }

                    if ((propEditing is ITable || propOther is ITable) &&
                        ForeignKeyEquals(property, tobeEdit, propEditing, other, propOther, keyProperties))
                        continue;

                    if (property.CanWrite)
                    {
                        property.SetValue(tobeEdit, propOther, EmptyParams);
                        var text = TrackPropertyChanges(tobeEdit, property, propEditing, propOther, user);
                        if (text != null)
                        {
                            historyText.Append(text);
                            historyText.Append("<BR />");
                        }
                    }
                }
                else
                {
                    if (!AreCollectionEquals(property.PropertyType, propEditing, propOther))
                    {
                        ApplyCollectionChanges(propEditing, propOther);
                        var text = TrackCollectionPropertyChanges(tobeEdit, property, user);
                        if (text != null)
                        {
                            historyText.Append(text);
                            historyText.Append("<BR />");
                        }
                    }
                }
            }

            history.Text = historyText.ToString();

            return history;
        }

        private static bool ForeignKeyEquals(PropertyInfo property, object tobeEdit, object propEditing, object other, object propOther, Dictionary<string, PropertyInfo> keyProperties)
        {
            var key = string.Format("{0}Id", property.Name);
            if (keyProperties.ContainsKey(key))
            {
                var pinfo = keyProperties[key];
                var fEdit = propEditing as ITable;
                var fOther = propOther as ITable;

                if (fEdit == null && fOther == null)
                    return true;
                else if (fEdit == null && fOther != null)
                {
                    var value = pinfo.GetValue(tobeEdit, null);
                    return value == null ? false : fOther.ID == (Guid)value;
                }
                else if (fEdit != null && fOther == null)
                {
                    var value = pinfo.GetValue(other, null);
                    return value == null ? false : fEdit.ID == (Guid)value;
                }
                else
                    return fEdit.ID == fOther.ID;
            }
            else
            {
                return false;
            }
        }

        private static void ApplyCollectionChanges(object propEditing, object propOther)
        {
            if (propEditing is IList)
            {
                var lEditing = (IList)propEditing;
                var lOther = (IList)propOther;

                lEditing.Clear();
                foreach (var child in lOther)
                {
                    lEditing.Add(child);
                }
            }
        }

        private static bool IsCollectionProperty(Type type)
        {
            // 对于不支持ICollection<>接口的属性，我们都默认认为这些集合属性是不可改动的
            type = type.GetInterface("ICollection`1");
            return type != null;
        }
        
        private static bool AreCollectionEquals(Type type, object propEditing, object propOther)
        {
            var collectionType = type.GetInterface("ICollection`1");
            // 先对比两个集合的元素数目是否一致
            var countProperty = collectionType.GetProperty("Count");
            var countLeft = (int)countProperty.GetValue(propEditing, EmptyParams);
            var countRight = (int)countProperty.GetValue(propOther, EmptyParams);

            if (countLeft != countRight)
                return false;

            // 继续对比集合中每个元素是否一致
            var left = (propEditing as IEnumerable).GetEnumerator();
            var right = (propOther as IEnumerable).GetEnumerator();

            left.Reset();
            right.Reset();
            while (left.MoveNext() && right.MoveNext())
            {
                if (!AreEquals(left.Current, right.Current))
                    return false;
            }

            return true;
        }

        private static PropertyInfoEqualityComparer _propertyCompaper = new PropertyInfoEqualityComparer();
        private static PropertyInfo[] GetRidOfDefaultProperties(object tobeEdit, string[] ignoreProperties)
        {
            Debug.Assert(tobeEdit != null);
            var type = tobeEdit.GetType();
            if (EntityHelpers.IsEntityProxyType(type))
                type = type.BaseType;

            var list = new List<PropertyInfo>(type.GetProperties());
            
            if (tobeEdit is ISupportDefaultProperties)
                list = RemovePropertiesInInterface(list, typeof(ISupportDefaultProperties));

            if (tobeEdit is ICommentable)
                list = RemovePropertiesInInterface(list, typeof(ICommentable));

            if (tobeEdit is ISupportOws)
                list = RemovePropertiesInInterface(list, typeof(ISupportOws));

            if ( ignoreProperties != null )
                list = RemovePropertiesInInterface(list, ignoreProperties);

            return list.OrderByDescending(p => p.Name).ToArray();
        }

        private static List<PropertyInfo> RemovePropertiesInInterface(List<PropertyInfo> list, string[] ignoreProperties)
        {
            for (int i = 0; i < list.Count; )
            {
                foreach (var propertyName in ignoreProperties)
                {
                    if (string.Compare(list[i].Name, propertyName, false) == 0)
                    {
                        list.RemoveAt(i);
                        --i;
                        break;
                    }
                }

                ++i;
            }

            return list;
        }

        private static List<PropertyInfo> RemovePropertiesInInterface(List<PropertyInfo> list, Type type)
        {
            var properties = type.GetProperties();

            for (int i = 0; i < list.Count; )
            {
                if (properties.Any(p => _propertyCompaper.Equals(p, list[i])))
                    list.RemoveAt(i);
                else
                    i++;
            }

            return list;
        }

        private static bool AreEquals(object left, object right)
        {
            if (left is DateTime && right is DateTime)
                return AreDateTimeEquals((DateTime)left, (DateTime)right);
            else if (left is ITable && right is ITable)
                return Guid.Equals(((ITable)left).ID, ((ITable)right).ID);
            else
                return object.Equals(left, right);
        }

        private static bool AreDateTimeEquals(DateTime left, DateTime right)
        {
            return left.Year == right.Year &&
                left.Month == right.Month &&
                left.Day == right.Day &&
                left.Hour == right.Hour &&
                left.Minute == right.Minute &&
                left.Second == right.Second;
        }

        private static string TrackCollectionPropertyChanges(
            INamedTable item, PropertyInfo property, IUser user)
        {
            var displayName = GetPropertyDisplayName(item, property);

            return string.Format("{0} 修改了属性 {1}", user.GetIdentifier(), displayName);
        }

        private static Comment CreateHistoryHelper(INamedTable item, IUser user)
        {
            var history = new Comment()
            {
                PostId = item.ID,
                PostType = EntityHelpers.GetEntityTypeName(item.GetType()),
                User = user.GetIdentifier()
            };

            return history;
        }

        private static string TrackPropertyChanges(
            INamedTable item, PropertyInfo property, 
            object oldValue, object newValue, IUser user)
        {            
            string displayName = GetPropertyDisplayName(item, property);
            var userName = user.GetIdentifier();
            var oldValueText = oldValue is IConvertible ? Convert.ToString(oldValue) : oldValue == null ? null : oldValue.ToString();
            var newValueText = newValue is IConvertible ? Convert.ToString(newValue) : newValue == null ? null : newValue.ToString();

            if (oldValue is INamedTable)
                oldValueText = (oldValue as INamedTable).Title;
            if (newValue is INamedTable)
                newValueText = (newValue as INamedTable).Title;

            return string.Format("属性 {0} 被 {1} 从 {2} 修改成 {3}",
                displayName, userName, oldValueText, newValueText);
        }

        private static string GetPropertyDisplayName(INamedTable item, PropertyInfo property)
        {
            var attrs = property.GetCustomAttributes(typeof(DisplayAttribute), false);
            var displayName = property.Name;

            if (attrs == null || attrs.Count() == 0)
                Trace.TraceError(string.Format("属性 {0} 应该附上Display属性，来指明它的显示名称！", property.Name));
            else
                displayName = ((DisplayAttribute)attrs.First()).Name;

            return displayName;
        }

        private static Comment TrackAddObject<U>(U item, IUser user)
            where U : class, INamedTable, ISupportDefaultProperties
        {
            var history = CreateHistoryHelper(item, user);
            history.Text = string.Format("{0}: 用户{1}创建{2}",
                item.OpenDate, history.User, item.Title);
            history.CommentDate = item.OpenDate;

            return history;
        }
    }

    class PropertyInfoEqualityComparer : IEqualityComparer<PropertyInfo>
    {
        public bool Equals(PropertyInfo x, PropertyInfo y)
        {
            if (x == null && y == null)
                return true;
            else if (x != null && y == null)
                return false;
            else if (x == null && y != null)
                return false;
            else
                // 一般来说,公开的属性在类型里是不可能重名的,重名只有一个原因,就是隐式实现接口造成的
                return string.Compare(x.Name, y.Name) == 0;
                    //&& x.PropertyType == y.PropertyType;
        }

        public int GetHashCode(PropertyInfo obj)
        {
            return obj.GetHashCode();
        }
    }

}